﻿/////////////////////////////////////////////////////////////////
//
// Physics Helper XAML
//
// by Andy Beaulieu - http://www.andybeaulieu.com
//
// LICENSE: This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
/////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FarseerPhysics.Collision;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using System.Threading.Tasks;
using WinUI = Windows.UI;
using SysWin = Windows.Foundation;
using SysWinF = Windows.UI.Xaml;
using UIShapes = Windows.UI.Xaml.Shapes;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using SysWinMedia = Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;
using Windows.Foundation;
using Windows.UI.Xaml.Media;

namespace Spritehand.FarseerHelper
{
    /// <summary>
    /// Various utilities for physics simulations and games
    /// </summary>
    public class PhysicsUtilities
    {

        public static PhysicsCanvas FindPhysicsCanvas(SysWinF.FrameworkElement element)
        {
            if (element != null)
            {
                if (element is PhysicsCanvas)
                {
                    return (element as PhysicsCanvas);
                }
                else
                {
                    element = SysWinMedia.VisualTreeHelper.GetParent(element) as SysWinF.FrameworkElement;
                    return FindPhysicsCanvas(element);
                }
            }

            return null;
        }

        public static Vector2 GetOffsetPositionInScreen(PhysicsSprite spr)
        {
            Vector2 offset = new Vector2();

            offset.X = (float)Canvas.GetLeft(spr);
            offset.Y = (float)Canvas.GetTop(spr);

            SysWinF.FrameworkElement parent = spr.Parent as SysWinF.FrameworkElement;

            while (parent != null)
            {
                offset.X += (float)Canvas.GetLeft(parent);
                offset.Y += (float)Canvas.GetTop(parent);

                if (parent is PhysicsCanvas)
                {
                    // we found the top canvas, break here.
                    break;
                }


                parent = parent.Parent as SysWinF.FrameworkElement;
            }

            return offset;
        }

        public static double DistanceToLine(SysWin.Point lineStart, SysWin.Point lineEnd, SysWin.Point point)
        {
            double xDelta = lineEnd.X - lineStart.X;
            double yDelta = lineEnd.Y - lineStart.Y;

            double u = ((point.X - lineStart.X) * xDelta + (point.Y - lineStart.Y) * yDelta) / (xDelta * xDelta + yDelta * yDelta);

            SysWin.Point closestPoint;
            if (u < 0)
                closestPoint = lineStart;
            else if (u > 1)
                closestPoint = lineEnd;
            else
                closestPoint = new SysWin.Point(lineStart.X + u * xDelta, lineStart.Y + u * yDelta);

            return DistanceBetweenTwoPoints(closestPoint, point);  // closestPoint.DistanceTo(point);
        }

        /// <summary>
        /// Convert degrees to radians
        /// </summary>
        /// <param name="angle">The degree value to convert</param>
        /// <returns>Radian value</returns>
        public static double DegreesToRadians(double angle)
        {
            return Math.PI * angle / 180.0;
        }

        public static double RadiansToDegrees(double radians)
        {
            double degrees = (180 / Math.PI) * radians;
            return (degrees);
        }

        /// <summary>
        /// Get the distance, in pixels between two points.
        /// </summary>
        public static double DistanceBetweenTwoPoints(SysWin.Point pt1, SysWin.Point pt2)
        {

            double a = pt2.X - pt1.X;
            double b = pt2.Y - pt1.Y;
            return Math.Sqrt(a * a + b * b);
        }


        /// <summary>
        /// A utility function to get a point that is a given distance and angle from another point.
        /// </summary>
        /// <param name="angle">The angle to travel</param>
        /// <param name="distance">The distance (in pixels) to go</param>
        /// <param name="ptStart">The start point</param>
        /// <returns></returns>
        public static SysWin.Point GetPointFromDistanceAngle(double angle, double distance, SysWin.Point ptStart)
        {
            double theta = angle * 0.0174532925;
            SysWin.Point p = new SysWin.Point();
            p.X = ptStart.X + distance * Math.Cos(theta);
            p.Y = ptStart.Y + distance * Math.Sin(theta);
            return p;
        }

        public static string EnsureUniqueName(Canvas cnv, PhysicsSprite sprToAdd, string name)
        {
            int suffix = 1;
            string thisName = name;
            Object possibleMatch = cnv.FindName(name);
            while (possibleMatch != null && possibleMatch != sprToAdd)
            {
                name = thisName + "_" + suffix.ToString();
                suffix++;

                possibleMatch = cnv.FindName(name);
            }
            return name;
        }

        public static void EnsureUniqueNames(SysWinF.DependencyObject uc, Guid ucOffset, int collisionGroup, List<PhysicsSprite> spriteList)
        {
            for (int i = 0; i < SysWinMedia.VisualTreeHelper.GetChildrenCount(uc); i++)
            {
                SysWinF.DependencyObject thisChild = SysWinMedia.VisualTreeHelper.GetChild(uc, i);


                if (thisChild is PhysicsSprite)
                {
                    PhysicsSprite spr = (thisChild as PhysicsSprite);
                    spr.OriginalName = spr.Name;
                    spr.Name = spr.Name + "_" + ucOffset.ToString();
                    spr.InternalName = spr.Name;

                    if (collisionGroup > 0 && spr.CollisionGroup > 0)
                    {
                        spr.CollisionGroup = collisionGroup;
                    }
                    spriteList.Add(spr);
                }
                else
                    if (thisChild is PhysicsJoint)
                    {
                        PhysicsJoint joint = (thisChild as PhysicsJoint);
                        joint.BodyOne = joint.BodyOne + "_" + ucOffset.ToString();
                        joint.BodyTwo = joint.BodyTwo + "_" + ucOffset.ToString();
                        if (collisionGroup > 0 && joint.CollisionGroup > 0)
                        {
                            joint.CollisionGroup = collisionGroup;
                        }
                    }
                    else
                        if (thisChild is SysWinF.FrameworkElement)
                        {
                            SysWinF.FrameworkElement child = (thisChild as SysWinF.FrameworkElement);
                            if (child.Name != null && child.Name != string.Empty)
                            {
                                string newName = child.Name + "_" + ucOffset.ToString();

                                child.Name = newName;
                            }

                        }

                if (thisChild is SysWinF.FrameworkElement)
                {
                    foreach (Object x in (thisChild as SysWinF.FrameworkElement).Resources)
                    {
#if WINDOWS_PHONE || SILVERLIGHT
                        if (x is System.Collections.DictionaryEntry)
                        {
                            Object itemValue = ((System.Collections.DictionaryEntry)x).Value;

#else
                        if (x is System.Collections.Generic.KeyValuePair<object, object>)
                        {
                            Object itemValue = ((System.Collections.Generic.KeyValuePair<object, object>)x).Value;
#endif

                            if (itemValue is Storyboard)
                            {
                                string currName = Convert.ToString((itemValue as Storyboard).GetValue(Canvas.NameProperty));
                                (itemValue as Storyboard).SetValue(Canvas.NameProperty, currName + "_" + ucOffset.ToString());
                            }
                        }

                    }
                }

                EnsureUniqueNames(thisChild, ucOffset, collisionGroup, spriteList);
            }


        }

        public static void DebugVisualTree(SysWinF.DependencyObject cnv, int level)
        {
            // tab in
            string tabIn = string.Empty;
            for (int i = 0; i < level * 4; i++)
            {
                tabIn += " ";
            }
            level++;

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < SysWinMedia.VisualTreeHelper.GetChildrenCount(cnv); i++)
            {
                SysWinF.DependencyObject thisChild = SysWinMedia.VisualTreeHelper.GetChild(cnv, i);

                sb.Clear();
                sb.Append(tabIn);
                sb.Append(thisChild.GetValue(Canvas.NameProperty));
                sb.Append(" [Type = " + thisChild.GetType().ToString() + "] ");
                if (thisChild is FrameworkElement)
                {
                    sb.Append(" [Visibility = " + (thisChild as FrameworkElement).Visibility.ToString() + "] ");
                    sb.Append(" [Opacity = " + (thisChild as FrameworkElement).Opacity.ToString() + "] ");
                    sb.Append(" [Transform = " + (thisChild as FrameworkElement).RenderTransform.ToString() + "] ");
                }
                //if (thisChild is Path)
                //{
                //    PathGeometry pg = (thisChild as Path).Data as PathGeometry;
                //    int fig = 1;
                //    foreach (PathFigure pf in pg.Figures)
                //    {

                //        sb.Append(" [figure " + fig.ToString() + ", start = " pf.StartPoint.ToString() + pf.Segments[0].X + "] ");
                //    }
                //}

                System.Diagnostics.Debug.WriteLine(sb.ToString());
                DebugVisualTree(thisChild, level);
            }
        }

        public static void DebugSpriteList(System.Collections.Generic.Dictionary<string, PhysicsSprite>.ValueCollection sprites, bool activeOnly)
        {
            System.Diagnostics.Debug.WriteLine("------Listing all Sprites------");
            foreach (PhysicsSprite spr in sprites)
            {
                if (activeOnly == false || (activeOnly && spr.BodyObject.Enabled == true))
                    System.Diagnostics.Debug.WriteLine("    " + spr.Name);
            }
        }

        /// <summary>
        /// Create a path object based on a list of vertices - used when creating geometry
        /// </summary>
        public static UIShapes.Shape CreatePathFromVertices(Body body, PhysicsSprite element, WinUI.Color colorFill)
        {
            List<Fixture> vertices = body.FixtureList;

            // is this an ellipse?
            if (vertices[0].Shape is CircleShape)
            {
                CircleShape cs = vertices[0].Shape as CircleShape;
                UIShapes.Ellipse el = new UIShapes.Ellipse();
                Vector2 screenSize = PhysicsCanvas.BoundaryHelper.WorldSizeToScreenSize(new Vector2(cs.Radius * 2, cs.Radius * 2));

                el.Width = screenSize.X;
                el.Height = screenSize.Y;

                el.Stroke = new SysWinMedia.SolidColorBrush(new WinUI.Color() { A = 255, R = 76, G = 108, B = 76 });
                el.StrokeThickness = 4;
                el.StrokeStartLineCap = SysWinMedia.PenLineCap.Round;
                el.Fill = new SysWinMedia.SolidColorBrush(colorFill);
                return el;
            }
            else
            {
                UIShapes.Path path = new UIShapes.Path();
                path.Stroke = new SysWinMedia.SolidColorBrush(new WinUI.Color() { A = 255, R = 76, G = 108, B = 76 });
                path.StrokeThickness = 4;
                path.StrokeStartLineCap = SysWinMedia.PenLineCap.Round;
                path.Fill = new SysWinMedia.SolidColorBrush(colorFill);


                SysWinMedia.PathGeometry pathGeom = new SysWinMedia.PathGeometry();
                SysWinMedia.PathFigureCollection figures = new SysWinMedia.PathFigureCollection();
                pathGeom.Figures = figures;

                double halfWidth = element.Width / 2d;
                double halfHeight = element.Height / 2d;
                if (halfWidth == 0) halfWidth = element.ActualWidth / 2d;
                if (halfHeight == 0) halfHeight = element.ActualHeight / 2d;

                foreach (Fixture f in vertices)
                {

                    if (f.Shape is PolygonShape)
                    {
                        SysWinMedia.PathFigure figure = new SysWinMedia.PathFigure();
                        PolygonShape p = f.Shape as PolygonShape;

                        // first vertex, create the startpoint
                        Vector2 ptStart = new Vector2((float)p.Vertices[0].X, (float)p.Vertices[0].Y);
                        ptStart = PhysicsCanvas.BoundaryHelper.WorldSizeToScreenSize(ptStart);
                        ptStart.X += (float)(halfWidth);
                        ptStart.Y += (float)(halfHeight);

                        figure.StartPoint = new SysWin.Point(ptStart.X, ptStart.Y);
                        figure.Segments = new SysWinMedia.PathSegmentCollection();
                        pathGeom.Figures.Add(figure);

                        for (int i = 1; i < p.Vertices.Count; i++)
                        {
                            SysWinMedia.LineSegment line = new SysWinMedia.LineSegment();
                            Vector2 pt = new Vector2((float)p.Vertices[i].X, (float)p.Vertices[i].Y);
                            pt = PhysicsCanvas.BoundaryHelper.WorldSizeToScreenSize(pt);
                            pt.X += (float)(halfWidth);
                            pt.Y += (float)(halfHeight);
                            line.Point = new SysWin.Point(pt.X, pt.Y); ;

                            figure.Segments.Add(line);
                        }

                    }

                }

                path.Data = pathGeom;
                return path;


            }
        }

        /// <summary>
        /// When sprites are added into the game canvas, if they have storyboards targeting them then 
        /// this method will redirect to the INSTANCE of the target instead of the NAME.
        /// </summary>
        /// <param name="uc"></param>
        public static void RedirectStoryboardTargets(SysWinF.FrameworkElement uc)
        {

            foreach (object item in uc.Resources)
            {
#if WINDOWS_PHONE || SILVERLIGHT
                if (item is System.Collections.DictionaryEntry)
                {
                    Object itemValue = ((System.Collections.DictionaryEntry)item).Value;

#else
                if (item is System.Collections.Generic.KeyValuePair<object, object>)
                {
                    Object itemValue = ((System.Collections.Generic.KeyValuePair<object, object>)item).Value;
#endif
                    if (itemValue is Storyboard)
                    {
                        Storyboard sb = itemValue as Storyboard;
                        foreach (Timeline tl in sb.Children)
                        {
                            string target = tl.GetValue(Storyboard.TargetNameProperty).ToString();
                            SysWinF.UIElement targetElement = uc.FindName(target) as SysWinF.UIElement;
                            if (targetElement != null)
                                Storyboard.SetTarget(tl, targetElement);

                        }
                    }
                }
            }

            // now do any children
            for (int i = 0; i < SysWinMedia.VisualTreeHelper.GetChildrenCount(uc); i++)
            {
                SysWinF.DependencyObject child = SysWinMedia.VisualTreeHelper.GetChild(uc, 0);
                if (child is SysWinF.FrameworkElement)
                {
                    RedirectStoryboardTargets(child as SysWinF.FrameworkElement);
                }
            }

        }

        public static float GetMaxCollisionForce(Contact contact)
        {
            float maxImpulse = 0.0f;
            Manifold manifold;
            contact.GetManifold(out manifold);
            for (int i = 0; i < manifold.PointCount; ++i)
            {
                maxImpulse = Math.Max(maxImpulse, manifold.Points[i].NormalImpulse);
            }

            return maxImpulse;

        }

        public static double GetAngleBetween(SysWin.Point pt1, SysWin.Point pt2)
        {
            double retval = 57.2727272727272727 * Math.Atan2(pt2.X - pt1.X, pt2.Y - pt1.Y);
            return retval;
        }

        public static double NormalizeAngle(double angle)
        {
            if (angle < 0 || angle > Math.PI * 2)
                return Math.Abs((Math.PI * 2) - Math.Abs(angle));
            else
                return angle;
        }


        public static double RadiansToDecimalDegrees(double radians)
        {

            var pi = Math.PI;

            return radians * (180 / pi);
        }

        public static double DecimalDegreesToRadians(double degrees)
        {

            var pi = Math.PI;

            return degrees * (pi / 180);

        }

        public static List<Joint> FindJoint(PhysicsCanvas cnvGame, string body1, string body2)
        {
            List<Joint> joint = new List<Joint>();

            foreach (Joint x in cnvGame.Simulator.JointList)
            {
                if ((x.BodyA == cnvGame.PhysicsObjects[body1].BodyObject && x.BodyB == cnvGame.PhysicsObjects[body2].BodyObject)
                || (x.BodyB == cnvGame.PhysicsObjects[body1].BodyObject && x.BodyA == cnvGame.PhysicsObjects[body2].BodyObject))
                {
                    joint.Add(x);

                }
            }


            return joint;
        }

        /// <summary>
        /// Gets a list of all joint-related sprites, given a sprite.
        /// </summary>
        public static void GetAllSpritesRelatedByJoints(PhysicsCanvas cnvGame, PhysicsSprite spr, List<PhysicsSprite> sprJoined)
        {
            // add in the given sprite
            if (sprJoined.IndexOf(spr) < 0)
                sprJoined.Add(spr);

            foreach (Joint joint in cnvGame.Simulator.JointList)
            {
                if (joint.BodyA == spr.BodyObject || joint.BodyB == spr.BodyObject)
                {
                    var spritesJoined = from s in cnvGame.PhysicsObjects.Values
                                        where s.BodyObject == joint.BodyA || s.BodyObject == joint.BodyB
                                        && (joint.BodyA.Enabled == true && joint.BodyB.Enabled == true)
                                        select s;
                    foreach (PhysicsSprite sprFound in spritesJoined)
                    {
                        if (sprJoined.IndexOf(sprFound) < 0)
                        {
                            GetAllSpritesRelatedByJoints(cnvGame, sprFound, sprJoined);
                        }
                    }
                }
            }

        }


        public static bool IsPointInside(Rect rect, Vector2 pt)
        {
            return (pt.X > rect.Left && pt.X < rect.Right &&
                    pt.Y > rect.Top && pt.Y < rect.Bottom);
        }

    }
}
